home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / gnu / emacs.lha / emacs-19.16 / lisp / ispell.el < prev    next >
Lisp/Scheme  |  1993-06-13  |  19KB  |  595 lines

  1. ;;; ispell.el --- this is the GNU EMACS interface to GNU ISPELL version 4.
  2.  
  3. ;;Copyright (C) 1990, 1991 Free Software Foundation, Inc.
  4.  
  5. ;; Keywords: wp
  6.  
  7. ;;This file is part of GNU Emacs.
  8. ;;
  9. ;;GNU Emacs is free software; you can redistribute it and/or modify
  10. ;;it under the terms of the GNU General Public License as published by
  11. ;;the Free Software Foundation; either version 2, or (at your option)
  12. ;;any later version.
  13. ;;
  14. ;;GNU Emacs is distributed in the hope that it will be useful,
  15. ;;but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. ;;GNU General Public License for more details.
  18. ;;
  19. ;;You should have received a copy of the GNU General Public License
  20. ;;along with GNU Emacs; see the file COPYING.  If not, write to
  21. ;;the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  22.  
  23. ;;; Commentary:
  24.  
  25. ;; This package provides a graceful interface to ispell, the GNU
  26. ;; spelling checker.
  27.  
  28. ;;; Code:
  29.  
  30. (defvar ispell-have-new-look t
  31.   "Non-nil means use the `-r' option when running `look'.")
  32.  
  33. (defvar ispell-enable-tex-parser nil
  34.   "Non-nil enables experimental TeX parser in Ispell for TeX-mode buffers.")
  35.  
  36. (defvar ispell-process nil "The process running Ispell")
  37. (defvar ispell-next-message nil
  38.   "An integer: where in `*ispell*' buffer to find next message from Ispell.")
  39.  
  40. (defvar ispell-command "ispell"
  41.   "Command for running Ispell.")
  42. (defvar ispell-command-options nil
  43.   "*String (or list of strings) to pass to Ispell as command arguments.
  44. You can use this to specify the name of your private dictionary.
  45. The -S option is always passed to Ispell as the last parameter,
  46. and need not be mentioned here.")
  47.  
  48. ;Each marker in this list points to the start of a word that
  49. ;ispell thought was bad last time it did the :file command.
  50. ;Notice that if the user accepts or inserts a word into his
  51. ;private dictionary, then some "good" words will be on the list.
  52. ;We would like to deal with this by looking up the words again just before
  53. ;presenting them to the user, but that is too slow on machines
  54. ;without the select system call.  Therefore, see the variable
  55. ;ispell-recently-accepted.
  56. (defvar ispell-bad-words nil
  57.   "A list of markers reflecting the output of the Ispell `:file' command.")
  58.  
  59. ;list of words that the user has accepted, but that might still
  60. ;be on the bad-words list
  61. (defvar ispell-recently-accepted nil)
  62.  
  63. ;; Non-nil means we have started showing an alternatives window.
  64. ;; This is the window config from before then.
  65. (defvar ispell-window-configuration)
  66.  
  67. ;t when :dump command needed
  68. (defvar ispell-dump-needed nil)
  69.  
  70. (defun ispell-flush-bad-words ()
  71.   (while ispell-bad-words
  72.     (if (markerp (car ispell-bad-words))
  73.         (set-marker (car ispell-bad-words) nil))
  74.     (setq ispell-bad-words (cdr ispell-bad-words)))
  75.   (setq ispell-recently-accepted nil))
  76.  
  77. (defun kill-ispell ()
  78.   "Kill the Ispell process.
  79. Any changes in your private dictionary
  80. that have not already been dumped will be lost."
  81.   (interactive)
  82.   (if ispell-process
  83.       (delete-process ispell-process))
  84.   (setq ispell-process nil)
  85.   (ispell-flush-bad-words))
  86.  
  87. (put 'ispell-startup-error 'error-conditions
  88.      '(ispell-startup-error error))
  89. (put 'ispell-startup-error 'error-message
  90.      "Problem starting ispell - see buffer *ispell*")
  91.  
  92. ;; Start an ispell subprocess; check the version; and display the greeting.
  93.  
  94. (defun start-ispell ()
  95.   (message "Starting ispell ...")
  96.   (let ((buf (get-buffer "*ispell*")))
  97.     (if buf
  98.     (kill-buffer buf)))
  99.   (condition-case err
  100.       (setq ispell-process
  101.         (apply 'start-process "ispell" "*ispell*" ispell-command
  102.            (append (if (listp ispell-command-options)
  103.                    ispell-command-options
  104.                  (list ispell-command-options))
  105.                '("-S"))))
  106.     (file-error (signal 'ispell-startup-error nil)))
  107.   (process-kill-without-query ispell-process)
  108.   (buffer-disable-undo (process-buffer ispell-process))
  109.   (accept-process-output ispell-process)
  110.   (let (last-char)
  111.     (save-excursion
  112.       (set-buffer (process-buffer ispell-process))
  113.       (bury-buffer (current-buffer))
  114.       (setq last-char (- (point-max) 1))
  115.       (while (not (eq (char-after last-char) ?=))
  116.     (cond ((not (eq (process-status ispell-process) 'run))
  117.            (kill-ispell)
  118.            (signal 'ispell-startup-error nil)))
  119.     (accept-process-output ispell-process)
  120.     (setq last-char (- (point-max) 1)))
  121.       (goto-char (point-min))
  122.       (let ((greeting (read (current-buffer))))
  123.     (if (not (= (car greeting) 1))
  124.         (error "Bad ispell version: wanted 1, got %d" (car greeting)))
  125.     (message (car (cdr greeting))))
  126.       (delete-region (point-min) last-char))))
  127.   
  128. ;; Make sure ispell is ready for a command.
  129. ;; Leaves buffer set to *ispell*, point at '='.
  130.  
  131. (defun ispell-sync (intr)
  132.   (if (or (null ispell-process)
  133.       (not (eq (process-status ispell-process) 'run)))
  134.       (start-ispell))
  135.   (if intr
  136.       (interrupt-process ispell-process))
  137.   (let (last-char)
  138.     (set-buffer (process-buffer ispell-process))
  139.     (bury-buffer (current-buffer))
  140.     (setq last-char (- (point-max) 1))
  141.     (while (not (eq (char-after last-char) ?=))
  142.       (accept-process-output ispell-process)
  143.       (setq last-char (- (point-max) 1)))
  144.     (goto-char last-char)))
  145.  
  146. ;; Send a command to ispell.  Choices are:
  147. ;; 
  148. ;; WORD        Check spelling of WORD.  Result is
  149. ;; 
  150. ;;             nil               not found
  151. ;;             t               spelled ok
  152. ;;             list of strings           near misses
  153. ;; 
  154. ;; :file FILENAME    scan the named file, and print the file offsets of
  155. ;;         any misspelled words
  156. ;; 
  157. ;; :insert WORD    put word in private dictionary
  158. ;; 
  159. ;; :accept WORD    don't complain about word any more this session
  160. ;; 
  161. ;; :dump        write out the current private dictionary, if necessary.
  162. ;; 
  163. ;; :reload        reread `~/ispell.words'
  164. ;; 
  165. ;; :tex
  166. ;; :troff
  167. ;; :generic    set type of parser to use when scanning whole files
  168.  
  169. (defun ispell-cmd (&rest strings)
  170.   (save-excursion
  171.     (ispell-sync t)
  172.     (set-buffer (process-buffer ispell-process))
  173.     (bury-buffer (current-buffer))
  174.     (erase-buffer)
  175.     (setq ispell-next-message (point-min))
  176.     (while strings
  177.       (process-send-string ispell-process (car strings))
  178.       (setq strings (cdr strings)))
  179.     (process-send-string ispell-process "\n")
  180.     (accept-process-output ispell-process)
  181.     (ispell-sync nil)))
  182.  
  183. (defun ispell-dump ()
  184.   (cond (ispell-dump-needed
  185.      (setq ispell-dump-needed nil)
  186.      (ispell-cmd ":dump"))))
  187.  
  188. (defun ispell-insert (word)
  189.   (ispell-cmd ":insert " word)
  190.   (if ispell-bad-words
  191.       (setq ispell-recently-accepted (cons word ispell-recently-accepted)))
  192.   (setq ispell-dump-needed t))
  193.  
  194. (defun ispell-accept (word)
  195.   (ispell-cmd ":accept " word)
  196.   (if ispell-bad-words
  197.       (setq ispell-recently-accepted (cons word ispell-recently-accepted))))
  198.  
  199. ;; Return the next message sent by the Ispell subprocess.
  200.  
  201. (defun ispell-next-message ()
  202.   (save-excursion
  203.     (set-buffer (process-buffer ispell-process))
  204.     (bury-buffer (current-buffer))
  205.     (save-restriction
  206.       (goto-char ispell-next-message)
  207.       (narrow-to-region (point)
  208.                         (progn (forward-sexp 1) (point)))
  209.       (setq ispell-next-message (point))
  210.       (goto-char (point-min))
  211.       (read (current-buffer)))))
  212.  
  213. (defun ispell-tex-buffer-p ()
  214.   (memq major-mode '(plain-TeX-mode LaTeX-mode)))
  215.  
  216. ;;;###autoload
  217. (defun ispell (&optional buf start end)
  218.   "Run Ispell over current buffer's visited file.
  219. First the file is scanned for misspelled words, then Ispell
  220. enters a loop with the following commands for every misspelled word:
  221.  
  222. DIGIT    Near miss selector.  If the misspelled word is close to
  223.     some words in the dictionary, they are offered as near misses.
  224. r    Replace.  Replace the word with a string you type.  Each word
  225.     of your new string is also checked.
  226. i    Insert.  Insert this word in your private dictionary (kept in
  227.     `$HOME/ispell.words').
  228. a    Accept.  Accept this word for the rest of this editing session,
  229.      but don't put it in your private dictionary.
  230. l    Lookup.  Look for a word in the dictionary by fast binary
  231.     search, or search for a regular expression in the dictionary
  232.     using grep.
  233. SPACE    Accept the word this time, but complain if it is seen again.
  234. q, \\[keyboard-quit]    Leave the command loop.  You can come back later with \\[ispell-next]."
  235.   (interactive)
  236.   (if (null start)
  237.       (setq start 0))
  238.   (if (null end)
  239.       (setq end 0))
  240.  
  241.   (if (null buf)
  242.       (setq buf (current-buffer)))
  243.   (setq buf (get-buffer buf))
  244.   (if (null buf)
  245.       (error "Can't find buffer"))
  246.   ;; Deactivate the mark, because we'll do it anyway if we change something,
  247.   ;; and a region highlight while in the Ispell loop is distracting.
  248.   (if transient-mark-mode
  249.       (progn
  250.     (setq mark-active nil)
  251.     (run-hooks 'deactivate-mark-hook)))
  252.   (save-excursion
  253.     (set-buffer buf)
  254.     (let ((filename buffer-file-name)
  255.       (delete-temp nil))
  256.       (unwind-protect
  257.       (progn
  258.         (cond ((null filename)
  259.            (setq filename (make-temp-name "/usr/tmp/ispell"))
  260.            (setq delete-temp t)
  261.            (write-region (point-min) (point-max) filename))
  262.           ((and (buffer-modified-p buf)
  263.             (y-or-n-p (format "Save file %s? " filename)))
  264.            (save-buffer)))
  265.         (message "Ispell scanning file...")
  266.         (if (and ispell-enable-tex-parser
  267.              (ispell-tex-buffer-p))
  268.         (ispell-cmd ":tex")
  269.           (ispell-cmd ":generic"))
  270.         (ispell-cmd (format ":file %s %d %d" filename start end)))
  271.     (if delete-temp
  272.         (condition-case ()
  273.         (delete-file filename)
  274.           (file-error nil)))))
  275.     (message "Parsing ispell output ...")
  276.     (ispell-flush-bad-words)
  277.     (let (pos bad-words)
  278.       (while (numberp (setq pos (ispell-next-message)))
  279.     ;;ispell may check the words on the line following the end
  280.     ;;of the region - therefore, don't record anything out of range
  281.     (if (or (= end 0)
  282.         (< pos end))
  283.         (setq bad-words (cons (set-marker (make-marker) (+ pos 1))
  284.                   bad-words))))
  285.       (setq bad-words (cons pos bad-words))
  286.       (setq ispell-bad-words (nreverse bad-words))))
  287.   (cond ((not (markerp (car ispell-bad-words)))
  288.      (setq ispell-bad-words nil)
  289.      (message "No misspellings."))
  290.     (t
  291.      (message "Ispell parsing done.")
  292.      (ispell-next))))
  293.  
  294. ;;;###autoload
  295. (defalias 'ispell-buffer 'ispell)
  296.  
  297. (defun ispell-next ()
  298.   "Resume command loop for most recent Ispell command."
  299.   (interactive)
  300.   (setq ispell-window-configuration nil)
  301.   (unwind-protect
  302.       (catch 'quit
  303.     ;; There used to be a save-excursion here,
  304.     ;; but that was annoying: it's better if point doesn't move
  305.     ;; when you type q.
  306.     (let (next)
  307.       (while (markerp (setq next (car ispell-bad-words)))
  308.         (switch-to-buffer (marker-buffer next))
  309.         (push-mark)
  310.         (ispell-point next "at saved position.")
  311.         (setq ispell-bad-words (cdr ispell-bad-words))
  312.         (set-marker next nil))))
  313.     (if ispell-window-configuration
  314.     (set-window-configuration ispell-window-configuration))
  315.     (cond ((null ispell-bad-words)
  316.        (error "Ispell has not yet been run."))
  317.       ((markerp (car ispell-bad-words))
  318.        (message (substitute-command-keys
  319.                        "Type \\[ispell-next] to continue.")))
  320.       ((eq (car ispell-bad-words) nil)
  321.        (setq ispell-bad-words nil)
  322.        (message "No more misspellings (but checker was interrupted.)"))
  323.       ((eq (car ispell-bad-words) t)
  324.        (setq ispell-bad-words nil)
  325.        (message "Ispell done."))
  326.       (t
  327.        (setq ispell-bad-words nil)
  328.        (message "Bad ispell internal list"))))
  329.   (ispell-dump))
  330.  
  331. ;;;###autoload
  332. (defun ispell-word (&optional resume)
  333.   "Check the spelling of the word under the cursor.
  334. See the command `ispell' for more information.
  335. With a prefix argument, resume handling of the previous Ispell command."
  336.   (interactive "P")
  337.   (if resume
  338.       (ispell-next)
  339.     (condition-case err
  340.     (catch 'quit
  341.       (save-window-excursion
  342.         (ispell-point (point) "at point."))
  343.       (ispell-dump))
  344.       (ispell-startup-error
  345.        (cond ((y-or-n-p "Problem starting ispell, use old-style spell instead? ")
  346.           (load-library "spell")
  347.           (define-key esc-map "$" 'spell-word)
  348.           (spell-word)))))))
  349. ;;;###autoload
  350. (define-key esc-map "$" 'ispell-word)
  351.  
  352. ;;;###autoload
  353. (defun ispell-region (start &optional end)
  354.   "Check the spelling for all of the words in the region."
  355.   (interactive "r")
  356.   (ispell (current-buffer) start end))
  357.  
  358. (defun ispell-letterp (c)
  359.   (and c
  360.        (or (and (>= c ?A) (<= c ?Z))
  361.        (and (>= c ?a) (<= c ?z))
  362.        (>= c 128))))
  363.  
  364. (defun ispell-letter-or-quotep (c)
  365.   (and c
  366.        (or (and (>= c ?A) (<= c ?Z))
  367.        (and (>= c ?a) (<= c ?z))
  368.        (= c ?')
  369.        (>= c 128))))
  370.  
  371. (defun ispell-find-word-start ()
  372.   ;;backward to a letter
  373.   (if (not (ispell-letterp (char-after (point))))
  374.       (while (and (not (bobp))
  375.           (not (ispell-letterp (char-after (- (point) 1)))))
  376.     (backward-char)))
  377.   ;;backward to beginning of word
  378.   (while (ispell-letter-or-quotep (char-after (- (point) 1)))
  379.     (backward-char))
  380.   (skip-chars-forward "'"))
  381.  
  382. (defun ispell-find-word-end ()
  383.   (while (ispell-letter-or-quotep (char-after (point)))
  384.     (forward-char))
  385.   (skip-chars-backward "'"))
  386.  
  387. (defun ispell-next-word ()
  388.   (while (and (not (eobp))
  389.           (not (ispell-letterp (char-after (point)))))
  390.     (forward-char)))
  391.  
  392. ;if end is nil, then do one word at start
  393. ;otherwise, do all words from the beginning of the word where
  394. ;start points, to the end of the word where end points
  395. (defun ispell-point (start message)
  396.   (let ((wend (make-marker))
  397.     rescan
  398.     end)
  399.     ;; There used to be a save-excursion here,
  400.     ;; but that was annoying: it's better if point doesn't move
  401.     ;; when you type q.
  402.     (goto-char start)
  403.     (ispell-find-word-start)        ;find correct word start
  404.     (setq start (point-marker))
  405.     (ispell-find-word-end)        ;now find correct end
  406.     (setq end (point-marker))
  407.     (if (>= start end)
  408.     (error "No word %s" message))
  409.     (while (< start end)
  410.       (goto-char start)
  411.       (ispell-find-word-end)        ;find end of current word
  412.                     ;could be before 'end' if
  413.                     ;user typed replacement
  414.                     ;that is more than one word
  415.       (set-marker wend (point))
  416.       (setq rescan nil)
  417.       (setq word (buffer-substring start wend))
  418.       (cond ((ispell-still-bad word)
  419. ;;; This just causes confusion. -- rms.
  420. ;;;         (goto-char start)
  421. ;;;         (sit-for 0)
  422.          (message (format "Ispell checking %s" word))
  423.          (ispell-cmd word)
  424.          (let ((message (ispell-next-message)))
  425.            (cond ((eq message t)
  426.               (message "%s: ok" word))
  427.              ((or (null message)
  428.               (consp message))
  429.               (setq rescan
  430.                 (ispell-command-loop word start wend message)))
  431.              (t
  432.               (error "unknown ispell response %s" message))))))
  433.       (cond ((null rescan)
  434.          (goto-char wend)
  435.          (ispell-next-word)
  436.          (set-marker start (point)))))
  437.     ;;clear the choices buffer; otherwise it's hard for the user to tell
  438.     ;;when we get back to the command loop
  439.     (let ((buf (get-buffer "*ispell choices*")))
  440.       (cond (buf
  441.          (set-buffer buf)
  442.          (erase-buffer))))
  443.     (set-marker start nil)
  444.     (set-marker end nil)
  445.     (set-marker wend nil)))
  446.   
  447. (defun ispell-still-bad (word)
  448.   (let ((words ispell-recently-accepted)
  449.     (ret t)
  450.     (case-fold-search t))
  451.     (while words
  452.       (cond ((eq (string-match (car words) word) 0)
  453.          (setq ret nil)
  454.          (setq words nil)))
  455.       (setq words (cdr words)))
  456.     ret))
  457.  
  458. (defun ispell-show-choices (word message first-line)
  459.   ;;if there is only one window on the frame, make the ispell
  460.   ;;messages winow be small.  otherwise just use the other window
  461.   (let* ((selwin (selected-window))
  462.      (resize (eq selwin (next-window)))
  463.      (buf (get-buffer-create "*ispell choices*"))
  464.      w)
  465.     (or ispell-window-configuration
  466.     (setq ispell-window-configuration (current-window-configuration)))
  467.     (setq w (display-buffer buf))
  468.     (buffer-disable-undo buf)
  469.     (if resize
  470.     (unwind-protect
  471.         (progn
  472.           (select-window w)
  473.           (enlarge-window (- 6 (window-height w))))
  474.       (select-window selwin)))
  475.     (save-excursion
  476.       (set-buffer buf)
  477.       (bury-buffer buf)
  478.       (set-window-point w (point-min))
  479.       (set-window-start w (point-min))
  480.       (erase-buffer)
  481.       (insert first-line "\n")
  482.       (insert
  483.        "SPC skip; A accept; I insert; DIGIT select; R replace; \
  484. L lookup; Q quit\n")
  485.       (cond ((not (null message))
  486.          (let ((i 0))
  487.            (while (< i 3)
  488.          (let ((j 0))
  489.            (while (< j 3)
  490.              (let* ((n (+ (* j 3) i))
  491.                 (choice (nth n message)))
  492.                (cond (choice
  493.                   (let ((str (format "%d %s" n choice)))
  494.                 (insert str)
  495.                 (insert-char ?  (- 20 (length str)))))))
  496.              (setq j (+ j 1))))
  497.          (insert "\n")
  498.          (setq i (+ i 1)))))))))
  499.  
  500. (defun ispell-command-loop (word start end message)
  501.   (let ((flag t)
  502.     (rescan nil)
  503.     first-line)
  504.     (if (null message)
  505.     (setq first-line (concat "No near misses for '" word "'"))
  506.       (setq first-line (concat "Near misses for '" word "'")))
  507.     (while flag
  508.       (ispell-show-choices word message first-line)
  509.       (message "Ispell command: ")
  510.       (undo-boundary)
  511.       (let ((c (downcase (read-char)))
  512.         replacement)
  513.     (cond ((and (>= c ?0)
  514.             (<= c ?9)
  515.             (setq replacement (nth (- c ?0) message)))
  516.            (ispell-replace start end replacement)
  517.            (setq flag nil))
  518.           ((= c ?q)
  519.            (throw 'quit nil))
  520.           ((= c ? )
  521.            (setq flag nil))
  522.           ((= c ?r)
  523.            (ispell-replace start end (read-string "Replacement: "))
  524.            (setq rescan t)
  525.            (setq flag nil))
  526.           ((= c ?i)
  527.            (ispell-insert word)
  528.            (setq flag nil))
  529.           ((= c ?a)
  530.            (ispell-accept word)
  531.            (setq flag nil))
  532.           ((= c ?l)
  533.            (let ((val (ispell-do-look word)))
  534.          (setq first-line (car val))
  535.          (setq message (cdr val))))
  536.           ((= c ??)
  537.            (message
  538.         "Type 'C-h d ispell' to the emacs main loop for more help")
  539.            (sit-for 2))
  540.           (t
  541.            (message "Bad ispell command")
  542.            (sit-for 2)))))
  543.     rescan))
  544.  
  545. (defun ispell-do-look (bad-word)
  546.   (let (regex buf words)
  547.     (cond ((null ispell-have-new-look)
  548.        (setq regex (read-string "Lookup: ")))
  549.       (t
  550.        (setq regex (read-string "Lookup (regex): " "^"))))
  551.     (setq buf (get-buffer-create "*ispell look*"))
  552.     (save-excursion
  553.       (set-buffer buf)
  554.       (delete-region (point-min) (point-max))
  555.       (if ispell-have-new-look
  556.       (call-process "look" nil buf nil "-r" regex)
  557.     (call-process "look" nil buf nil regex))
  558.       (goto-char (point-min))
  559.       (forward-line 10)
  560.       (delete-region (point) (point-max))
  561.       (goto-char (point-min))
  562.       (while (not (= (point-min) (point-max)))
  563.     (end-of-line)
  564.     (setq words (cons (buffer-substring (point-min) (point)) words))
  565.     (forward-line)
  566.     (delete-region (point-min) (point)))
  567.       (kill-buffer buf)
  568.       (cons (format "Lookup '%s'" regex)
  569.         (reverse words)))))
  570.     
  571. (defun ispell-replace (start end new)
  572.   (goto-char start)
  573.   (insert new)
  574.   (delete-region (point) end))
  575.  
  576. (defun reload-ispell ()
  577.   "Tell Ispell to re-read your private dictionary."
  578.   (interactive)
  579.   (ispell-cmd ":reload"))
  580.  
  581. (defun batch-make-ispell ()
  582.   (byte-compile-file "ispell.el")
  583.   (find-file "ispell.texinfo")
  584.   (let ((old-dir default-directory)
  585.     (default-directory "/tmp"))
  586.     (texinfo-format-buffer))
  587.   (Info-validate)
  588.   (if (get-buffer " *problems in info file*")
  589.       (kill-emacs 1))
  590.   (write-region (point-min) (point-max) "ispell.info"))
  591.  
  592. (provide 'ispell)
  593.  
  594. ;;; ispell.el ends here
  595.